package application.adapter;

import application.anno.TreeViewLayout;
import application.exception.NotFoundNecessaryAnnotationException;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.Pane;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class TreeAdapter<T> {

    private TreeView<Pane> treeView;

    @Getter
    @Setter
    private String rootName;

    @Getter
    @Setter
    private String itemName;

    @Getter
    private Object rootController;

    /**
     * 若小使用TreeAdapter，则需要携带信息的实体类，继承此对象。
     */
    @Data
    public static class TreeEntry {

        /**
         * 当此变量被修改为true时，则表示此Item为根节点
         */
        protected boolean isRoot;

        /**
         * 一个标识，建议使用UUID作为标识。
         * 它表示，若此节点作为父类节点时，此变量的值，应该与子节点的parentFlag保持一致。
         * 若无子节点与其保持一致，则自动判定其，没有子节点。
         */
        protected String flag;

        /**
         * 一个标识，它的内容，应该与其父节点的TreeEntry中的flag对象的值，保持一致。
         * TreeAdapter通过比对此对象与其他TreeEntry的flag，来判断当前对象表示的节点，是否是子节点，并将其绘制到对应位置。
         * 若此为null或者为空字符串时，表示此Entry对象，没有父节点
         */
        protected String parentFlag;

        /**
         * 此对象由Adapter自己内部操作，无需外部开发者调整。
         */
        protected List<TreeEntry> entries;

        public TreeEntry() {
            this.entries = new ArrayList<>();
        }

        protected void clearEntries() {
            if (this.entries == null) {
                return;
            }
            this.entries.clear();
        }
    }

    private Map<Pane, Object> itemControllers;

    public TreeAdapter(TreeView<Pane> treeView) throws NotFoundNecessaryAnnotationException {
        this.treeView = treeView;
        itemControllers = new HashMap<>();
        TreeViewLayout annotation = this.getClass().getAnnotation(TreeViewLayout.class);
        if (annotation == null) {
            throw new NotFoundNecessaryAnnotationException("Not found TreeViewRoot annotation");
        }
        String cacheRoot = annotation.rootName();
        if (cacheRoot.isEmpty()) {
            throw new NotFoundNecessaryAnnotationException("Not found root layout information");
        }
        String cacheItem = annotation.itemName();
        if (cacheItem.isEmpty()) {
            throw new NotFoundNecessaryAnnotationException("Not found item layout information");
        }
        this.rootName = cacheRoot;
        this.itemName = cacheItem;
    }

    public TreeAdapter(TreeView<Pane> treeView, String rootName, String itemName) {
        this.treeView = treeView;
        itemControllers = new HashMap<>();
        this.rootName = rootName;
        this.itemName = itemName;
    }

    /**
     * 获取子节点的对应Controller
     *
     * @param pane 子节点布局
     * @param <C>  子节点控制器泛型
     * @return 子节点控制器
     */
    public <C> C getItemController(Pane pane) {
        return (C) this.itemControllers.get(pane);
    }

    /**
     * 将数据填充到TreeView中
     *
     * @param entryMap map的key为TreeEntry的flag value为TreeEntry本身
     */
    @SneakyThrows
    public void setItems(Map<String, TreeEntry> entryMap) {
        TreeEntry rootEntry = null;
        for (Map.Entry<String, TreeEntry> entryEntry : entryMap.entrySet()) {
            TreeEntry value = entryEntry.getValue();
            if (value.isRoot) {
                rootEntry = value;
                continue;
            }
            String parentFlag = value.getParentFlag();
            TreeEntry treeEntry = entryMap.get(parentFlag);
            if (treeEntry != null) {
                treeEntry.entries.add(value);
            }
        }
        if (rootEntry == null) {
            return;
        }
        Pane rootPane = getRootPane(rootName);
        TreeItem<Pane> rootItem = new TreeItem<>(rootPane);
        showTreeView(rootEntry, rootItem);
        treeView.setRoot(rootItem);
    }

    /**
     * 递归填充TreeView视图
     *
     * @param rootEntry 携带数据的视图实体类
     * @param treeItem  要填充数据的视图。
     */
    @SneakyThrows
    protected void showTreeView(TreeEntry rootEntry, TreeItem<Pane> treeItem) {
        List<TreeEntry> entries = rootEntry.entries;
        if (!entries.isEmpty()) {
            for (TreeEntry treeEntry : entries) {
                if (treeEntry.equals(rootEntry)) {
                    continue;
                }
                Pane itemPan = null;
                Object o = null;
                if (treeEntry.entries.isEmpty()) {
                    itemPan = getItemPan(itemName);
                    o = this.itemControllers.get(itemPan);
                } else {
                    itemPan = getRootPane(rootName);
                    o = this.rootController;
                }
                Method init = o.getClass().getDeclaredMethod("init", TreeEntry.class);
                init.setAccessible(true);
                init.invoke(o, treeEntry);
                TreeItem<Pane> entry = new TreeItem<>(itemPan);
                treeItem.getChildren().add(entry);
                showTreeView(treeEntry, entry);
            }
        }
    }

    /**
     * 获取根节点布局
     *
     * @param rootName 根节点布局资源路径
     * @return 根节点布局对象
     * @throws IOException IO异常
     */
    protected Pane getRootPane(String rootName) throws IOException {
        if (rootName == null) {
            throw new NotFoundNecessaryAnnotationException("Not found necessary information");
        }
        URL resource = this.getClass().getClassLoader().getResource(rootName);
        if (resource == null) {
            throw new NotFoundNecessaryAnnotationException("Not found necessary resource");
        }
        FXMLLoader fxmlLoader = new FXMLLoader(resource);
        Pane pane = fxmlLoader.load();
        rootController = fxmlLoader.getController();
        return pane;
    }

    /**
     * 获取子节点布局
     *
     * @param itemName 子节点布局资源路径
     * @return 子节点布局对象
     * @throws IOException IO异常
     */
    protected Pane getItemPan(String itemName) throws IOException {
        if (rootName == null) {
            throw new NotFoundNecessaryAnnotationException("Not found necessary information");
        }
        URL resource = this.getClass().getClassLoader().getResource(itemName);
        if (resource == null) {
            throw new NotFoundNecessaryAnnotationException("Not found necessary resource");
        }
        FXMLLoader fxmlLoader = new FXMLLoader(resource);
        Pane pane = fxmlLoader.load();
        Object controller = fxmlLoader.getController();
        this.itemControllers.put(pane, controller);
        return pane;
    }
}
